﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Eco.Handles;
using CommandGraph.Model;
using CommonClasses.Logger;
using CommonClasses.Utility;
using System.Drawing.Drawing2D;
using CommonClasses.Model;
using Eco.ObjectImplementation;
using EcoExtensions.Model;
using Eco.ObjectRepresentation;

namespace CommandGraph.Controls
{
    public partial class GraphEditorControl : UserControl
    {
        #region Private fields

        private IEcoServiceProvider ecoSpace;
        private ILogger logger;

        /// <summary>
        /// Граф, который мы отображаем
        /// </summary>
        private Graph graph = null;
        /// <summary>
        /// Коробка с инструментами, которые используются контролом.
        /// </summary>
        private ToolBox toolBox = null;
        /// <summary>
        /// Инструмент, управляющий отрисовкой графа
        /// </summary>
        private GraphPainterTool Painter
        {
            get
            {
                return toolBox.GetToolByName("GraphPainterTool") as GraphPainterTool;
            }
        }
        /// <summary>
        /// Инструмент, управляющий выделением элементов графа
        /// </summary>
        private SelectionTool Selection
        {
            get
            {
                return toolBox.GetToolByName("SelectionTool") as SelectionTool;
            }
        }
        /// <summary>
        /// Положение мыши
        /// </summary>
        private Point mouseLocation = Point.Empty;

        #endregion

        #region Public properties

        /// <summary>
        /// Граф, который представлен данным контролом.
        /// При присваивании нового значения контрол автоматически обновляется.
        /// </summary>
        public Graph Graph
        {
            get
            {
                return graph;
            }
            set
            {
                graph = value;
                RefreshContent();
            }
        }
        /// <summary>
        /// Холст, на котором рисуется граф.
        /// </summary>
        public Control Canvas
        {
            get
            {
                return canvas;
            }
        }
        /// <summary>
        /// Ящик с инструментами, используемый контролом
        /// </summary>
        public ToolBox ToolBox
        {
            get
            {
                return toolBox;
            }
        }
        /// <summary>
        /// Вертикальная полоса прокрутки
        /// </summary>
        public new VScrollBar VerticalScroll
        {
            get
            {
                return vScrollBar;
            }
        }
        /// <summary>
        /// Горизонтальная полоса прокрутки
        /// </summary>
        public new HScrollBar HorizontalScroll
        {
            get
            {
                return hScrollBar;
            }
        }

        #endregion

        public GraphEditorControl()
        {
            InitializeComponent();
            RethinkScrollParameters();
        }

        #region Public methods

        /// <summary>
        /// Проинициалиировать данный контрол для работы
        /// </summary>
        /// <param name="ecoSpace">ECO сервис-центр, с которым будет работать редактор графа</param>
        /// <param name="logger">Логгер, в который контрол будет сообщать об ошибках</param>
        public void Initialize(IEcoServiceProvider ecoSpace)
        {
            if (ecoSpace == null)
                new NullReferenceException("EcoSpace cannot be null!");

            this.ecoSpace = ecoSpace;
        }

        /// <summary>
        /// Обновить содержимое полей в соответствии с указанным графом (Graph)
        /// и перерисовать компонент.
        /// </summary>
        public void RefreshContent()
        {
            //if (ecoSpace == null)
            //throw new NullReferenceException("Initialize control first!");

            if (graph == null)
                return;

            InitializeToolBox();

            var entityManager = toolBox.GetToolByName("EntityManager") as EntityManager;
            foreach (var entity in graph.Entities)
                entityManager.PrepareEntity(entity);

            Refresh();

            canvas.Focus();
        }

        /// <summary>
        /// Добавить в граф элемент и отобразить его.
        /// </summary>
        /// <param name="entity">Элемент, который должен быть добавлен</param>
        public void AddEntity(GraphEntityBase entity)
        {
            (toolBox.GetToolByName("EntityManager") as EntityManager).AddEntity(entity);

            canvas.Focus();
        }

        /// <summary>
        /// Скомпилировать граф в LPMED
        /// </summary>
        /// <param name="compilationUnit">
        /// Модуль, полученный при компиляции графа
        /// </param>
        /// <returns>
        /// Отчёт об ошибках компиляции 
        /// <remarks>
        /// (здесь присутствуют только ошибки предметной области, например - наличие циклов)
        /// </remarks>
        /// </returns>
        [Obsolete("This method is used only for test application CommandGraphTest")]
        public ErrorReport Compile(out CommandsGraphCompilationUnit compilationUnit)
        {
            var code = string.Empty;
            var report = new ErrorReport(ecoSpace);

            compilationUnit = new CommandsGraphCompilationUnit(ecoSpace);
            var codeGenerator = new CodeGenerationTool(ecoSpace, null);

            if (graph is CommandsGraph)
            {

                report = codeGenerator.GenCode(graph as CommandsGraph, out code);
            }
            else
            {
                code = "not generated";
                report = new ErrorReport(ecoSpace);
                report.AddError("Current graph is not the commands graph! It's the "
                        + graph.GetType().ToString(),
                    new EcoPoint(ecoSpace, Point.Empty),
                    compilationUnit);
            }

            compilationUnit.LPMEDCode = code;
            return report;
        }

        #endregion

        #region Private methods

        private void InitializeToolBox()
        {
            toolBox = new ToolBox(ecoSpace);
            graph.ToolBox = toolBox;

            #region Main tools

            //инструмент для откатов и возвратов
            toolBox.AddTool("UndoTool",
                new UndoTool(ecoSpace,
                    canvas,
                    toolBox));

            //инструмент, отвечающий за непосредственное добавление и удаление элементов графа
            toolBox.AddTool("EntityManager",
                new EntityManager(ecoSpace,
                    canvas,
                    graph,
                    toolBox));
            //данный инструмент работает всегда
            toolBox.ActivateTool("EntityManager");

            //инструмент управления отрисовкой
            toolBox.AddTool("GraphPainterTool",
                new GraphPainterTool(ecoSpace,
                    canvas,     //на чём рисуем?
                    Graph,      //что рисуем?
                   toolBox)); //кто рисует выделение?
            //мы сразу начинаем рисовать граф
            toolBox.ActivateTool("GraphPainterTool");

            #endregion

            #region Edit existing tools

            //инструмент выделения
            toolBox.AddTool("SelectionTool",
                new SelectionTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            //инструмент перетаскивания элементов с помощью мыши
            toolBox.AddTool("MoveTool",
                new MoveTool(ecoSpace,
                    canvas,     //на чём таскаем?
                    toolBox)); //а чего таскаем-то?

            //инструмент добавления точек перегиба к любым переходам
            toolBox.AddTool("MoveAnchorPointTool",
                new MoveAnchorPointTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            //инструмент для изменения размеров вершин
            toolBox.AddTool("ResizeTool",
                new ResizeTool(ecoSpace,
                    canvas,
                    toolBox));
            toolBox.ActivateTool("ResizeTool");

            #endregion

            #region Adding transitions tools

            //инструмент добавления простых переходов типа Transition
            toolBox.AddTool("AddTransitionTool",
                new AddTransitionTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            //инструмент добавления точек перегиба к любым переходам
            toolBox.AddTool("AddInflectionTool",
                new AddInflectionTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));
            //данный инструмент всегда активен
            toolBox.ActivateTool("AddInflectionTool");

            //инструмент для добавления переходов - комментариев
            toolBox.AddTool("AddCommentTransitionTool",
                new AddCommentTransitionTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            #endregion

            #region Adding vertices tools

            //инструмент добавления вершин в граф
            toolBox.AddTool("AddVertexTool",
                new AddVertexTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            //инструмент добавления композитных вершин в граф
            toolBox.AddTool("AddCompositeVertexTool",
                new AddCompositeVertexTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            //инструмент для добавления вершин - комментариев
            toolBox.AddTool("AddCommentVertexTool",
                new AddCommentVertexTool(ecoSpace,
                    canvas,
                    graph,
                    toolBox));

            #endregion

            #region For Command Graph

            if (graph is CommandsGraph)
            {
                #region Elements

                //инструмент для добавления активных переходов
                toolBox.AddTool("AddActiveTransitionTool",
                    new AddActiveTransitionTool(ecoSpace,
                        canvas,
                        graph,
                        toolBox));

                //инструмент для добавления вершин - ожиданий
                toolBox.AddTool("AddWaitVertexTool",
                    new AddWaitVertexTool(ecoSpace,
                        canvas,
                        graph,
                        toolBox));

                //инструмент для добавления вершин - ожиданий
                toolBox.AddTool("AddIfVertexTool",
                    new AddIfVertexTool(ecoSpace,
                        canvas,
                        graph,
                        toolBox));


                //инструмент для добавления циклов
                toolBox.AddTool("AddLoopTool",
                    new AddLoopTool(ecoSpace,
                        canvas,
                        graph,
                        toolBox));

                //инструмент для добавления вершин типа "Начало"
                toolBox.AddTool("AddStartVertexTool",
                    new AddStartVertexTool(ecoSpace,
                        canvas,
                        graph,
                        toolBox));

                //инструмент для добавления вершин типа "Начало"
                toolBox.AddTool("AddFinishVertexTool",
                    new AddFinishVertexTool(ecoSpace,
                        canvas,
                        graph,
                        toolBox));
                #endregion

                //инструмент для компиляции во время редактирования
                toolBox.AddTool("EditTimeCompilationTool",
                    new EditTimeCompilationTool(ecoSpace,
                        graph as CommandsGraph,
                        toolBox));

                //инструмент для сохранения и загрузки графов
                toolBox.AddTool("CommandsGraphLoaderTool",
                    new CommandsGraphLoaderTool(ecoSpace,
                        canvas,
                        toolBox));
            }

            #endregion

            //Undo активируется в последнюю очередь
            toolBox.ActivateTool("UndoTool");

            //-----Инстурмент для копирования - не добавляется в ToolBox!!!-----\\
            new GraphCopyist(ecoSpace,
                canvas,
                toolBox,
                graph);
        }

        #endregion

        #region UserInput

        private void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            //клик левой кнопкой мыши
            if (e.Button == MouseButtons.Left)
                ProcessLeftClick(sender, e);

            else
                //прокрутка колёсика мыши
                if (e.Button == MouseButtons.Middle)
                    BeginScroll(e);
            //наводим фокус на контрол, чтобы можно было перехватывать события от клавы
            canvas.Focus();
        }

        private void canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (isScrolling)
                ProcessScroll(e);

            mouseLocation = e.Location;
        }

        private void canvas_MouseHover(object sender, EventArgs e)
        {
            if (isScrolling)
            {
                var location = canvas.PointToClient(Cursor.Position);
                ProcessScroll(new MouseEventArgs(MouseButtons.Middle,
                    0,
                    location.X,
                    location.Y,
                    0));
            }
            else
                SetToolTip();
        }

        private void canvas_MouseUp(object sender, MouseEventArgs e)
        {
            isScrolling = false;
        }

        private void canvas_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (e.Control)
                switch (e.KeyCode)
                {
                    case Keys.Z:
                        (toolBox.GetToolByName("UndoTool") as UndoTool).Undo();
                        break;
                    case Keys.Y:
                        (toolBox.GetToolByName("UndoTool") as UndoTool).Redo();
                        break;
                }
        }
        /// <summary>
        /// Обработать клик левой кнопкой мыши на холсте
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void ProcessLeftClick(object sender, MouseEventArgs e)
        {
            if (!(toolBox.GetToolByName("ResizeTool") as ResizeTool).Hits(e.Location))
            {
                //показывает, попали ли мы мышью во что-нибудь
                bool hit = false;

                //попадаем ли мы мышью во что-нибудь?
                for (int i = graph.Entities.Count - 1; i >= 0; i--)
                {
                    var entity = graph.Entities[i];

                    if (entity.Hits(e.Location))
                    {
                        hit = true;
                        if (entity is AnchorPoint)
                        {
                            toolBox.ActivateTool("MoveAnchorPointTool");
                            if ((entity as AnchorPoint).AttachedTransition.Selected)
                                break;
                        }
                        else
                        {
                            if (Selection.SelectedEntities.Contains(entity))
                            {
                                toolBox.ActivateTool("MoveTool");
                            }
                            else
                            {
                                Selection.Clear();
                                if (entity.CanSelect)
                                {
                                    Selection.AddEntity(entity); //entity.Select вызывает Selection
                                    toolBox.ActivateTool("MoveTool"); //сразу можем перемещать
                                }
                            }
                        }
                        entity.MouseClick(sender, e);
                        break;
                    }
                }

                //если никуда не попали, включаем инструмент выделения
                if (!hit)
                    toolBox.ActivateTool("SelectionTool");
            }
        }

        #endregion

        #region Paint

        private void Canvas_Paint(object sender, PaintEventArgs e)
        {
            if (Graph == null)
                return;

            Painter.Refresh(e);
        }

        #endregion

        #region Scrolling

        /// <summary>
        /// Показывает, производится ли на данный момент скролл мышью
        /// </summary>
        private bool isScrolling = false;
        /// <summary>
        /// Точка, в которой находилась мышь в момент начала скролла
        /// </summary>
        private Point scrollBeginLocation = Point.Empty;

        /// <summary>
        /// Пересчитать параметры полос прокрутки
        /// </summary>
        private void RethinkScrollParameters()
        {
            var horzExtra = canvas.Width - this.Width - vScrollBar.Width;
            var vertExtra = canvas.Height - this.Height - hScrollBar.Height;

            hScrollBar.LargeChange = 10;
            vScrollBar.LargeChange = 10;

            hScrollBar.Maximum = horzExtra;
            vScrollBar.Maximum = vertExtra;

            hScrollBar.Value = -Math.Max(-horzExtra, Math.Min(-hScrollBar.Value, 0));
            vScrollBar.Value = -Math.Max(-vertExtra, Math.Min(-vScrollBar.Value, 0));

            hScrollBar.Enabled = hScrollBar.Visible = horzExtra > 0;
            vScrollBar.Enabled = vScrollBar.Visible = vertExtra > 0;
        }
        /// <summary>
        /// Начать прокрутку с помощью мыши
        /// </summary>
        /// <param name="e">-</param>
        private void BeginScroll(MouseEventArgs e)
        {
            scrollBeginLocation = canvas.PointToScreen(e.Location);
            Cursor.Current = Cursors.NoMove2D;
            isScrolling = true;
        }
        /// <summary>
        /// Обработать прокрутку с помощью мыши (роликом)
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void ProcessScroll(MouseEventArgs e)
        {
            var curLocation = canvas.PointToScreen(e.Location);
            var deltaX = curLocation.X - scrollBeginLocation.X;
            var deltaY = curLocation.Y - scrollBeginLocation.Y;
            var bound = 10;
            var scale = 5.0e-3f;

            if ((Math.Abs(deltaX) < bound) && (Math.Abs(deltaY) < bound))
            {
                Cursor.Current = Cursors.NoMove2D;
                return;
            }

            if (Math.Abs(deltaX) < bound)
                Cursor.Current = (deltaY > 0) ?
                    Cursors.PanSouth
                    : Cursors.PanNorth;
            else if (Math.Abs(deltaY) < bound)
                Cursor.Current = (deltaX > 0) ?
                    Cursors.PanEast
                    : Cursors.PanWest;
            else if (deltaX * deltaY < 0)
                Cursor.Current = (deltaX > 0) ?
                    Cursors.PanNE
                    : Cursors.PanSW;
            else
                Cursor.Current = (deltaX > 0) ?
                    Cursors.PanSE
                    : Cursors.PanNW;

            hScrollBar.Value = Math.Max(hScrollBar.Minimum, 
                Math.Min((int)(hScrollBar.Value + deltaX * scale),
                    hScrollBar.Maximum));
            vScrollBar.Value = Math.Max(vScrollBar.Minimum,
                Math.Min((int)(vScrollBar.Value + deltaY * scale),
                    vScrollBar.Maximum));
        }

        private void GraphEditorControl_Resize(object sender, EventArgs e)
        {
            RethinkScrollParameters();
        }

        private void vScrollBar_ValueChanged(object sender, EventArgs e)
        {
            canvas.Location = new Point(canvas.Location.X, -vScrollBar.Value);
        }

        private void hScrollBar_ValueChanged(object sender, EventArgs e)
        {
            canvas.Location = new Point(-hScrollBar.Value, canvas.Location.Y);
        }

        private void vScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            vScrollBar_ValueChanged(sender, e);
        }

        private void hScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            hScrollBar_ValueChanged(sender, e);
        }

        #endregion

        private void SetToolTip()
        {
            GraphEntityBase entityWithToolTip = null;
            //e.Cancel = true;
            for (int i = graph.Entities.Count - 1; i >= 0; i--)
            {
                var pretender = graph.Entities[i];
                if (pretender.Hits(mouseLocation))
                {
                    if (pretender.ToolTipText != string.Empty)
                    {
                        entityWithToolTip = pretender;
                        break;
                    }
                }
            }

            if (entityWithToolTip == null)
                return;

            //вот это супер-пупер ф-я высшего порядка!!!
            Func<Control, Form> getParentWindow = null;
            getParentWindow = (control) =>
            {
                if (control.Parent is Form)
                    return control.Parent as Form;
                else
                    return getParentWindow(control.Parent);
            };

            var parentWindow = getParentWindow(this);

            //toolTip.Show(entityWithToolTip.ToolTipText, parentWindow, mouseLocation, 1000);
            //toolTip.SetToolTip(canvas, entityWithToolTip.ToolTipText);
        }

        private void toolTip_Popup(object sender, PopupEventArgs e)
        {
            SetToolTip();
        }
    }
}